/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.kunminx.architecture.domain.result;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;

import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * LinkedList, which pretends to be a map and supports modifications during iterations.
 * It is NOT thread safe.
 *
 * @param <K> Key type
 * @param <V> Value type
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public class SafeIterableMap<K, V> implements Iterable<Map.Entry<K, V>> {

  @SuppressWarnings("WeakerAccess") /* synthetic access */
          Entry<K, V> mStart;
  private Entry<K, V> mEnd;
  // using WeakHashMap over List<WeakReference>, so we don't have to manually remove
  // WeakReferences that have null in them.
  private WeakHashMap<SupportRemove<K, V>, Boolean> mIterators = new WeakHashMap<>();
  private int mSize = 0;

  protected Entry<K, V> get(K k) {
    Entry<K, V> currentNode = mStart;
    while (currentNode != null) {
      if (currentNode.mKey.equals(k)) {
        break;
      }
      currentNode = currentNode.mNext;
    }
    return currentNode;
  }

  /**
   * If the specified key is not already associated
   * with a value, associates it with the given value.
   *
   * @param key key with which the specified value is to be associated
   * @param v   value to be associated with the specified key
   * @return the previous value associated with the specified key,
   * or {@code null} if there was no mapping for the key
   */
  public V putIfAbsent(@NonNull K key, @NonNull V v) {
    Entry<K, V> entry = get(key);
    if (entry != null) {
      return entry.mValue;
    }
    put(key, v);
    return null;
  }

  protected Entry<K, V> put(@NonNull K key, @NonNull V v) {
    Entry<K, V> newEntry = new Entry<>(key, v);
    mSize++;
    if (mEnd == null) {
      mStart = newEntry;
      mEnd = mStart;
      return newEntry;
    }

    mEnd.mNext = newEntry;
    newEntry.mPrevious = mEnd;
    mEnd = newEntry;
    return newEntry;

  }

  /**
   * Removes the mapping for a key from this map if it is present.
   *
   * @param key key whose mapping is to be removed from the map
   * @return the previous value associated with the specified key,
   * or {@code null} if there was no mapping for the key
   */
  public V remove(@NonNull K key) {
    Entry<K, V> toRemove = get(key);
    if (toRemove == null) {
      return null;
    }
    mSize--;
    if (!mIterators.isEmpty()) {
      for (SupportRemove<K, V> iter : mIterators.keySet()) {
        iter.supportRemove(toRemove);
      }
    }

    if (toRemove.mPrevious != null) {
      toRemove.mPrevious.mNext = toRemove.mNext;
    } else {
      mStart = toRemove.mNext;
    }

    if (toRemove.mNext != null) {
      toRemove.mNext.mPrevious = toRemove.mPrevious;
    } else {
      mEnd = toRemove.mPrevious;
    }

    toRemove.mNext = null;
    toRemove.mPrevious = null;
    return toRemove.mValue;
  }

  /**
   * @return the number of elements in this map
   */
  public int size() {
    return mSize;
  }

  /**
   * @return an ascending iterator, which doesn't include new elements added during an
   * iteration.
   */
  @NonNull
  @Override
  public Iterator<Map.Entry<K, V>> iterator() {
    ListIterator<K, V> iterator = new AscendingIterator<>(mStart, mEnd);
    mIterators.put(iterator, false);
    return iterator;
  }

  /**
   * @return an descending iterator, which doesn't include new elements added during an
   * iteration.
   */
  public Iterator<Map.Entry<K, V>> descendingIterator() {
    DescendingIterator<K, V> iterator = new DescendingIterator<>(mEnd, mStart);
    mIterators.put(iterator, false);
    return iterator;
  }

  /**
   * return an iterator with additions.
   */
  public IteratorWithAdditions iteratorWithAdditions() {
    @SuppressWarnings("unchecked")
    IteratorWithAdditions iterator = new IteratorWithAdditions();
    mIterators.put(iterator, false);
    return iterator;
  }

  /**
   * @return eldest added entry or null
   */
  public Map.Entry<K, V> eldest() {
    return mStart;
  }

  /**
   * @return newest added entry or null
   */
  public Map.Entry<K, V> newest() {
    return mEnd;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (!(obj instanceof androidx.arch.core.internal.SafeIterableMap)) {
      return false;
    }
    androidx.arch.core.internal.SafeIterableMap map = (androidx.arch.core.internal.SafeIterableMap) obj;
    if (this.size() != map.size()) {
      return false;
    }
    Iterator<Map.Entry<K, V>> iterator1 = iterator();
    Iterator iterator2 = map.iterator();
    while (iterator1.hasNext() && iterator2.hasNext()) {
      Map.Entry<K, V> next1 = iterator1.next();
      Object next2 = iterator2.next();
      if ((next1 == null && next2 != null)
              || (next1 != null && !next1.equals(next2))) {
        return false;
      }
    }
    return !iterator1.hasNext() && !iterator2.hasNext();
  }

  @Override
  public int hashCode() {
    int h = 0;
    Iterator<Map.Entry<K, V>> i = iterator();
    while (i.hasNext()) {
      h += i.next().hashCode();
    }
    return h;
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("[");
    Iterator<Map.Entry<K, V>> iterator = iterator();
    while (iterator.hasNext()) {
      builder.append(iterator.next().toString());
      if (iterator.hasNext()) {
        builder.append(", ");
      }
    }
    builder.append("]");
    return builder.toString();
  }

  private abstract static class ListIterator<K, V> implements Iterator<Map.Entry<K, V>>,
          SupportRemove<K, V> {
    Entry<K, V> mExpectedEnd;
    Entry<K, V> mNext;

    ListIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
      this.mExpectedEnd = expectedEnd;
      this.mNext = start;
    }

    @Override
    public boolean hasNext() {
      return mNext != null;
    }

    @SuppressWarnings("ReferenceEquality")
    @Override
    public void supportRemove(@NonNull Entry<K, V> entry) {
      if (mExpectedEnd == entry && entry == mNext) {
        mNext = null;
        mExpectedEnd = null;
      }

      if (mExpectedEnd == entry) {
        mExpectedEnd = backward(mExpectedEnd);
      }

      if (mNext == entry) {
        mNext = nextNode();
      }
    }

    @SuppressWarnings("ReferenceEquality")
    private Entry<K, V> nextNode() {
      if (mNext == mExpectedEnd || mExpectedEnd == null) {
        return null;
      }
      return forward(mNext);
    }

    @Override
    public Map.Entry<K, V> next() {
      Map.Entry<K, V> result = mNext;
      mNext = nextNode();
      return result;
    }

    abstract Entry<K, V> forward(Entry<K, V> entry);

    abstract Entry<K, V> backward(Entry<K, V> entry);
  }

  static class AscendingIterator<K, V> extends ListIterator<K, V> {
    AscendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
      super(start, expectedEnd);
    }

    @Override
    Entry<K, V> forward(Entry<K, V> entry) {
      return entry.mNext;
    }

    @Override
    Entry<K, V> backward(Entry<K, V> entry) {
      return entry.mPrevious;
    }
  }

  private static class DescendingIterator<K, V> extends ListIterator<K, V> {

    DescendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
      super(start, expectedEnd);
    }

    @Override
    Entry<K, V> forward(Entry<K, V> entry) {
      return entry.mPrevious;
    }

    @Override
    Entry<K, V> backward(Entry<K, V> entry) {
      return entry.mNext;
    }
  }

  private class IteratorWithAdditions implements Iterator<Map.Entry<K, V>>, SupportRemove<K, V> {
    private Entry<K, V> mCurrent;
    private boolean mBeforeStart = true;

    IteratorWithAdditions() {
    }

    @SuppressWarnings("ReferenceEquality")
    @Override
    public void supportRemove(@NonNull Entry<K, V> entry) {
      if (entry == mCurrent) {
        mCurrent = mCurrent.mPrevious;
        mBeforeStart = mCurrent == null;
      }
    }

    @Override
    public boolean hasNext() {
      if (mBeforeStart) {
        return mStart != null;
      }
      return mCurrent != null && mCurrent.mNext != null;
    }

    @Override
    public Map.Entry<K, V> next() {
      if (mBeforeStart) {
        mBeforeStart = false;
        mCurrent = mStart;
      } else {
        mCurrent = mCurrent != null ? mCurrent.mNext : null;
      }
      return mCurrent;
    }
  }

  interface SupportRemove<K, V> {
    void supportRemove(@NonNull Entry<K, V> entry);
  }

  static class Entry<K, V> implements Map.Entry<K, V> {
    @NonNull
    final K mKey;
    @NonNull
    final V mValue;
    Entry<K, V> mNext;
    Entry<K, V> mPrevious;

    Entry(@NonNull K key, @NonNull V value) {
      mKey = key;
      this.mValue = value;
    }

    @NonNull
    @Override
    public K getKey() {
      return mKey;
    }

    @NonNull
    @Override
    public V getValue() {
      return mValue;
    }

    @Override
    public V setValue(V value) {
      throw new UnsupportedOperationException("An entry modification is not supported");
    }

    @Override
    public String toString() {
      return mKey + "=" + mValue;
    }

    @SuppressWarnings("ReferenceEquality")
    @Override
    public boolean equals(Object obj) {
      if (obj == this) {
        return true;
      }
      if (!(obj instanceof Entry)) {
        return false;
      }
      Entry entry = (Entry) obj;
      return mKey.equals(entry.mKey) && mValue.equals(entry.mValue);
    }

    @Override
    public int hashCode() {
      return mKey.hashCode() ^ mValue.hashCode();
    }
  }
}
